﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using LitJson;

[AddComponentMenu("PUEX/Affector/Physics/SphereCollider")]
public class PUEXSphereColliderAffector : PUEXBaseColliderAffector {

	#region - Editable property
	[Header("SphereCollider:")]
	public Vector3 center;
	public string centerParamName = "";
	public float radius;
	public string radiusParamName = "";
	public bool innerCollision;
	#endregion

	#region - Property
	protected Sphere _colliderSphere;
	#endregion
	
	#region - Lifecycle
	public PUEXSphereColliderAffector ()
	{
		center = Vector3.zero;
		radius = 20f;
		innerCollision = false;
	}
	#endregion
	
	#region - Update
	public override void preUpdate (float dt)
	{
		base.preUpdate (dt);

		Vector3 _colliderCenter = center;
		float _colliderRadius = radius;
		if (inWorldSpace) {
			if (ownerEmitter.keepLocal) {
				Matrix4x4 invMat = ownerEmitter.ownerParticleSystem.systemToWorldTransform.inverse;
				_colliderCenter = invMat.MultiplyPoint(_colliderCenter);
				_colliderRadius *= 1f / ownerEmitter.ownerParticleSystem.systemToWorldScale;
			}
		} else {
			if (!ownerEmitter.keepLocal) {
				_colliderCenter = ownerEmitter.ownerParticleSystem.systemToWorldTransform.MultiplyPoint(_colliderCenter);
				_colliderRadius *= ownerEmitter.ownerParticleSystem.systemToWorldScale;
			}
		}
		_colliderSphere = new Sphere (_colliderCenter, _colliderRadius);
	}

	public override void update (float dt, PUEXParticle particle)
	{
		Vector3 predictedPosition = particle.position + particle.velocity() * dt;
		bool collision = false;

		switch (intersectionType) {
		case IntersectionType.IT_SPHERE:
		{
			Sphere particleColliderSphere = new Sphere (predictedPosition, particle.boundingSphereRadius());
			
			// if (_colliderSphere.intersects (particleColliderSphere) && innerCollision == false)
			// OR
			// if (!_colliderSphere.intersects (particleColliderSphere) && innerCollision == true)
			if (_colliderSphere.intersects (particleColliderSphere) != innerCollision)
				collision = true;
			
			break;
		}
		case IntersectionType.IT_BOX:
		{
			Bounds particleColliderBox = new Bounds (predictedPosition, particle.dimensions ());
			
			// if (PUEXUtils.intersects (_colliderSphere, particleColliderBox) && innerCollision == false)
			// OR
			// if (!PUEXUtils.intersects (_colliderSphere, particleColliderBox) && innerCollision == true)
			if (PUEXUtils.intersects (_colliderSphere, particleColliderBox) != innerCollision)
				collision = true;
			
			break;
		}
		}

		if (collision)
		{
			particle.addEventFlags(PUEXParticle.PEF_COLLIDED);

			if (minKillSpeed > 0f && Mathf.Abs(particle.speed) <= minKillSpeed) {
				particle.kill();
			}

			if (minFreezeSpeed > 0f && !particle.freezed && Mathf.Abs(particle.speed) <= minFreezeSpeed) {
				particle.freeze ();
			}
			
			if (particle.timeToLive > 0f && !particle.freezed) {
				Vector3 distance = predictedPosition - _colliderSphere.center;

				switch (collisionType) {
				case CollisionType.CT_BOUNCE:
				{
					/** If the particle is on the surface (or just inside the sphere); bounce it
		                Make use of formula R = 2 * (-I dot N) * N + I, where
		                R = the new direction vector
		                I = the old (unit) direction vector before the collision
		                N = the Normal at the collision point
		            */
					Vector3 N = distance.normalized;
					particle.direction = 2f * (Vector3.Dot(-particle.direction, N)) * N + particle.direction;
					particle.speed *= bouncyness;
					
					break;
				}
				case CollisionType.CT_FLOW:
				{
					Plane colliderPlane = new Plane(distance.normalized, 
					                                _colliderSphere.center + distance * (_colliderSphere.radius / distance.magnitude));
					particle.direction = PUEXUtils.planeProjectVector(colliderPlane, particle.direction).normalized;
					particle.speed *= friction;
					
					break;
				}
				}
				
				calculateRotationSpeedAfterCollision(particle);
			}
		}
	}
	#endregion
	
	#region - Param property
	public override void updateParams (PUEXParticleSystem system)
	{
		Vector3 value3;
		if (system.getParamVec3 (centerParamName, out value3)) {
			center = value3;
		}
		float value;
		if (system.getParamFloat (radiusParamName, out value)) {
			radius = value;
		}
	}
	#endregion

	#region - Draw
	public void OnDrawGizmos()
	{
		if (!enabled)
			return;

		Matrix4x4 defaultMatrix = Gizmos.matrix;
		if (inWorldSpace) {
			Gizmos.matrix = Matrix4x4.identity;
		} else {
			Gizmos.matrix = transform.parent.transform.localToWorldMatrix;
		}
		
		Color defaultColor = Gizmos.color;
		Gizmos.color = new Color (Color.yellow.r, Color.yellow.g, Color.yellow.b, 0.3f);

		Gizmos.DrawSphere (center, radius);
		
		Gizmos.color = defaultColor;
		Gizmos.matrix = defaultMatrix;
	}
	#endregion

	#region - Serialization
	public override JsonData save (JsonData emitterData, JsonData systemData)
	{
		JsonData ret = base.save (emitterData, systemData);

		ret ["center"] = PUEXUtils.serializeVector3(center);
		ret ["center_param_name"] = centerParamName;
		ret ["radius"] = radius;
		ret ["radius_param_name"] = radiusParamName;
		ret ["inner_collision"] = innerCollision;
		
		return ret;
	}
	#endregion

}
